﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using WordsMatching;
using System.Text.RegularExpressions;
using NDESharp;
using System.Data;

namespace LFM_Playlist_Builder
{
    class WmlReader
    {

        String inpath = "";
        String outpath = "";
        float minArtistSimilarity = (float)0.5;
        float minSongSimilarity = (float)0.5;
        float minTotalSimilarity = (float)0.5;

        public List<TrackInfo> library = new List<TrackInfo>();

        public WmlReader(String iInpath)
        {
            inpath = iInpath;
        }

        public WmlReader(String iInpath, String iOutpath)
        {
            inpath = iInpath;
            outpath = iOutpath;
        }

        public WmlReader(String iInpath, float similarity)
        {
            inpath = iInpath;
            minArtistSimilarity = similarity;
            minSongSimilarity = similarity;
            minTotalSimilarity = similarity;
        }

        public WmlReader(String iInpath, String iOutpath, float similarity)
        {
            inpath = iInpath;
            outpath = iOutpath;
            minArtistSimilarity = similarity;
            minSongSimilarity = similarity;
            minTotalSimilarity = similarity;
        }

        public void setPaths(String iInpath, String iOutpath)
        {
            inpath = iInpath;
            outpath = iOutpath;
        }

        public List<TrackInfo> getLibrary()
        {
            return library;
        }

        public void readLibrary()
        {
            String line = "";
            String[] EXTSplitDelimiters = {","}; //We'll just use comma for now
            String[] SongSplitDelimiters = { " - " }; //We'll just use " - " for now
            String[] wholeString;
            Boolean error = false;

            byte[] theFile = Encoding.Convert(Encoding.Default, Encoding.Unicode, File.ReadAllBytes(inpath));
            wholeString = (Encoding.Unicode.GetString(theFile)).Split('\n');

            for (int i = 1 /*skip header*/; i < wholeString.Length-1; i++)
            {
                if (i % 2 == 1) // If it's a track info line
                {
                    String[] splitInfoEXT = wholeString[i].Split(EXTSplitDelimiters, 2, StringSplitOptions.None); //Splits the EXT from the front
                    String[] splitInfoSong = splitInfoEXT[1].Split(SongSplitDelimiters, 2, StringSplitOptions.None); //Splits song and artist
                    //TODO: fix splits so they work for tracks that do have delimiters in them. Currently will work for all tracks except ones that have a " - " in the artist name
                    if (splitInfoEXT.Length >= 2 && splitInfoSong.Length >= 2) //Make sure it split properly, we'll include ones that have split of greater than 3, just to see what kind of tracks show up in the output
                    {
                        library.Add(new TrackInfo(splitInfoSong[0].Trim(), splitInfoSong[1].Trim(), line, splitInfoEXT[0].Trim(), library.Count, Global.MLOADER));
                    }
                    else
                    {
                        //This should never occur, but in case it does
                        error = true;
                    }
                }
                else  // If it's a file location line
                {
                    if (!error)
                    {
                        library[library.Count - 1].setFileName(wholeString[i].Split('\r')[0]); //Get rid of the \r at the end of the filename
                    }
                    error = false;
                }
            }
        }

        public void readWinampLibrary()
        {
            NDEDatabase winampLib = new NDEDatabase(inpath);
            DataTable dt;

            dt = winampLib.SongDS.Tables[0];
            dt.TableName = "dt";

            foreach (DataRow i in dt.Rows)
            {
                //Seems there's a rubbish character at the start of the fields, remove the first character of each string

                String artist = i[2].ToString();
                String track = i[1].ToString();
                String filePath = i[0].ToString();
                String trackLength = i.Field<String>("length");

                if (artist.Length > Global.MINIMPORTSTRINGLENGTH && track.Length > Global.MINIMPORTSTRINGLENGTH && filePath.Length > Global.MINIMPORTSTRINGLENGTH)
                {
                    library.Add(new TrackInfo(i[2].ToString().Remove(0, 1), i[1].ToString().Remove(0, 1), i[0].ToString().Remove(0, 1), Global.EXTPRELUDE + trackLength, library.Count, Global.MLOADER));
                }

            }

        }

        public void alphabetizeThis()
        {
            library.Sort(delegate(TrackInfo track1, TrackInfo track2) { return track1.getArtist().CompareTo(track2.getArtist()); });
        }

        public void revertThis(List<TrackInfo> thePlaylist)
        {
            library.Sort(delegate(TrackInfo track1, TrackInfo track2) { return track1.getIndex().CompareTo(track2.getIndex()); });
        }

        public List<TrackInfo> removePlaylistDuplicates(List<TrackInfo> thePlaylist)
        {
            for (int trackNum = 0; trackNum < thePlaylist.Count; trackNum++)
            {
                if (thePlaylist[trackNum].getFileName() != "" && thePlaylist[trackNum].getFileName() != null) //Don't want to remove our null filename friends
                {
                    for (int i = trackNum + 1; i < thePlaylist.Count; i++) //Start at index, duplicates will only be from now on
                    {
                        if (thePlaylist[trackNum].getFileName() == thePlaylist[i].getFileName()) // If it's the same
                        {
                            thePlaylist.RemoveAt(i); //Remove item I
                        }
                    }
                }
            }
            return thePlaylist;
        }

        public List<TrackInfo> alphabetizePlaylist(List<TrackInfo> thePlaylist)
        {
            thePlaylist.Sort(delegate(TrackInfo track1, TrackInfo track2) { return track1.getArtist().CompareTo(track2.getArtist()); });
            return thePlaylist;
        }

        public List<TrackInfo> sortPlaylistByPlayCount(List<TrackInfo> thePlaylist)
        {
            thePlaylist.Sort(delegate(TrackInfo track1, TrackInfo track2) { return track1.getPlayCount().CompareTo(track2.getPlayCount()); });
            return thePlaylist;
        }

        public List<TrackInfo> revertPlaylist(List<TrackInfo> thePlaylist)
        {
            thePlaylist.Sort(delegate(TrackInfo track1, TrackInfo track2) { return track1.getIndex().CompareTo(track2.getIndex()); });
            return thePlaylist;
        }

        public List<TrackInfo> buildPlaylist(List<TrackInfo> LFMPlaylist) //Inputs a last FM playlist with filenames empty, Outputs a playlist
        {
            int index = 0; //Tracks are likely to be in a reasonable order, will be more efficient to not start at the same part every time TODO: Put all track arrays in full alphabetical order, then use artist similarity in conjunction with track similarity
            int librarySize = library.Count();
            int fullLoop = librarySize; //Here we can tell if a full loop has happened
            if (librarySize > 0) //Make sure there's stuff in the library
            {
                for (int i = 0; i < LFMPlaylist.Count(); i++) //Do for each track in the last.fm list
                {
                    while (index != fullLoop)
                    {
                        if (index >= librarySize)
                        {
                            index = 0;
                        }

                        if (LFMPlaylist[i].getArtist().ToLower() == library[index].getArtist().ToLower())
                        {

                            if (LFMPlaylist[i].getTrackName().ToLower() == library[index].getTrackName().ToLower())
                            {
                                LFMPlaylist[i].librarySet(library[index]);
                                LFMPlaylist[i].setFound(true);
                                fullLoop = index;
                                index++; //We can assume that the same track will not want to be found twice
                                break;
                            }
                        }

                        index++;
                    }

                    if (!LFMPlaylist[i].isFound()) //If a match wasn't found explicitly
                    {
                        index++;
                    }
                }
            }

            return LFMPlaylist;
        }

        /* Not efficient - currently not used
        //Checks if both artist and filename are similar
        public List<TrackInfo> buildPlaylistCorrection(List<TrackInfo> LFMPlaylist) //Inputs a last FM playlist, uses last.fm correction to find tracks
        {
            int index = 0;
            String lastArtist = "";
            Boolean artistMatch = false;
            int librarySize = library.Count();

            for (int i = 0; i < LFMPlaylist.Count(); i++) //Do again
            {
                //Only for things that have not been found
                if (!LFMPlaylist[i].isFound())
                {
                    index = 0;
                    while (index < librarySize) //Have to do it for all files anyway
                    {
                        if (LFMPlaylist[i].getArtist().ToLower() != lastArtist.ToLower()) //Checks if it's the same artist we checked last time. We don't want to be calling this for EVERY artist, last.fm probably wouldn't like that
                        {

                            //Find lastfm name
                            LastFMXML correction = new LastFMXML();
                            library[index] = correction.getCorrectionInfo(library[index]); //Update with better details

                            if (LFMPlaylist[i].getArtist().ToLower() == library[index].getArtist().ToLower())
                            {
                                if (LFMPlaylist[i].getTrackName().ToLower() == library[index].getTrackName().ToLower())
                                {
                                    LFMPlaylist[i] = library[index];
                                    LFMPlaylist[i].setFound(true);
                                    index++;
                                    break;
                                }
                            }
                        }
                        index++;
                    }
                }
            }

            return LFMPlaylist;
        }
         */

        public List<TrackInfo> buildPlaylistSimilar(List<TrackInfo> LFMPlaylist, bool checkArtists) //Inputs a last FM playlist with some filenames empty, will make playlist out of things that are not yet found. If check artists is set, will check artist similarity too.
        {
            int index = 0;
            int librarySize = library.Count();

            //Usually most artists are correct, it is just song names that are incorrect. As such, will run through once to check song names just for artists, then once more checking both artist and songname
            if (librarySize > 0) //Make sure there's stuff in the library
            {
                for (int i = 0; i < LFMPlaylist.Count(); i++) //Do for each track in the last.fm list
                {
                    //Only for things that have not been found or scored already
                    if (!LFMPlaylist[i].isFound())
                    {
                        while (index != librarySize)
                        {
                            if (index >= librarySize)
                            {
                                index = 0;
                            }

                            if (LFMPlaylist[i].getArtist().ToLower() == library[index].getArtist().ToLower() ||
                                "the " + LFMPlaylist[i].getArtist().ToLower() == library[index].getArtist().ToLower() || //missing or extra 'the's are a common error and this is an efficient way of checking this
                                LFMPlaylist[i].getArtist().ToLower() ==  "the " + library[index].getArtist().ToLower() ||
                                LFMPlaylist[i].getArtist().ToLower() + ", the"  ==  library[index].getArtist().ToLower() ||
                                LFMPlaylist[i].getArtist().ToLower() == library[index].getArtist().ToLower() + ", the")
                            {
                                MatchsMaker songMatchTest = new MatchsMaker(LFMPlaylist[i].getTrackName().ToLower(), library[index].getTrackName().ToLower());

                                float totalScore;
                                if (songMatchTest.Score > minSongSimilarity && (totalScore = (songMatchTest.Score)) > LFMPlaylist[i].getScore()) //Check that it's bigger than the score already in there
                                {
                                    LFMPlaylist[i].librarySet(library[index]);
                                    LFMPlaylist[i].setScore(totalScore);
                                }
                            }

                            index++;
                        }

                        if (!LFMPlaylist[i].isFound()) //If a match wasn't found explicitly
                        {
                            index++;
                        }
                    }

                    if (LFMPlaylist[i].getScore() > minArtistSimilarity)
                    {
                        LFMPlaylist[i].setFound(true);
                    }
                }

                if (checkArtists)
                {

                    //Let's check if the songname's correct but the artist is similar
                    for (int i = 0; i < LFMPlaylist.Count(); i++) //Do for each track in the last.fm list
                    {
                        //Only for things that have not been found or scored already
                        if (!LFMPlaylist[i].isFound() && LFMPlaylist[i].getScore() == (float)0.0)
                        {
                            while (index != librarySize)
                            {
                                if (index >= librarySize)
                                {
                                    index = 0;
                                }

                                if (LFMPlaylist[i].getTrackName().ToLower() == library[index].getTrackName().ToLower()) //Check if the trackname is correct
                                {
                                    MatchsMaker artistMatchTest = new MatchsMaker(LFMPlaylist[i].getArtist().ToLower(), library[index].getArtist().ToLower());

                                    float totalScore;
                                    if (artistMatchTest.Score > minSongSimilarity && (totalScore = (artistMatchTest.Score)) > minArtistSimilarity)
                                    {
                                        LFMPlaylist[i].librarySet(library[index]);
                                        LFMPlaylist[i].setScore(totalScore);
                                    }
                                }

                                index++;
                            }

                            if (!LFMPlaylist[i].isFound()) //If a match wasn't found explicitly
                            {
                                index++;
                            }
                        }

                        if (LFMPlaylist[i].getScore() > minArtistSimilarity)
                        {
                            LFMPlaylist[i].setFound(true);
                        }
                    }
                }
            }

            return LFMPlaylist;
        }

        public List<TrackInfo> buildPlaylistSimilarThorough(List<TrackInfo> LFMPlaylist) //Inputs a last FM playlist with some filenames empty, will make playlist out of things that are not yet found. Currently not efficient.
        {
            int index = 0;
            String lastArtist = "";
            float lastScore = (float)0.0;
            Boolean artistFound = false;
            int librarySize = library.Count();


            //only is efficient if both are ordered alphabetically - will solve issue of finding similar artist and songname
            //currently too inefficient for the amount of additional tracks created.
            for (int i = 0; i < LFMPlaylist.Count(); i++)
            {
                //Only for things that have not been found or scored already
                if ((!LFMPlaylist[i].isFound()) && (LFMPlaylist[i].getScore() == (float)0.0))
                {
                    index = 0;
                    while (index < librarySize) //Have to do it for all files anyway
                    {
                        if (artistFound && lastArtist != library[index].getArtist())
                        {
                            artistFound = false;
                        }

                        if (artistFound) //If an artist has been found - don't need to check for artist again
                        {
                            if (lastArtist.ToLower() != library[index].getArtist().ToLower())
                            {
                                artistFound = false;
                            }
                            MatchsMaker songMatchTest = new MatchsMaker(LFMPlaylist[i].getTrackName().ToLower(), library[index].getTrackName().ToLower());

                            float totalScore;
                            if (songMatchTest.Score > minSongSimilarity && (totalScore = ((songMatchTest.Score + lastScore) / 2)) > LFMPlaylist[i].getScore()) //Check that it's bigger than the score already in there
                            {
                                LFMPlaylist[i].librarySet(library[index]);
                                LFMPlaylist[i].setScore(totalScore);
                            }
                        }
                        else
                        { //new artist
                            if (lastArtist.ToLower() != library[index].getArtist().ToLower()) //Only test similarity with an artist that hasn't been checked yet
                            {
                                MatchsMaker artistMatchTest = new MatchsMaker(LFMPlaylist[i].getArtist().ToLower(), library[index].getArtist().ToLower());
                                lastScore = artistMatchTest.Score;
                                if (artistMatchTest.Score > minSongSimilarity && ((artistMatchTest.Score)) > minArtistSimilarity)
                                {
                                    artistFound = true;
                                    MatchsMaker songMatchTest = new MatchsMaker(LFMPlaylist[i].getTrackName().ToLower(), library[index].getTrackName().ToLower());

                                    float totalScore;
                                    if (songMatchTest.Score > minSongSimilarity && (totalScore = ((songMatchTest.Score + lastScore) / 2)) > LFMPlaylist[i].getScore()) //Check that it's bigger than the score already in there
                                    {
                                        LFMPlaylist[i].librarySet(library[index]);
                                        LFMPlaylist[i].setScore(totalScore);
                                    }

                                }
                            }
                        }
                        lastArtist = library[index].getArtist();

                        index++;
                    }
                }

                if (LFMPlaylist[i].getScore() > minArtistSimilarity)
                {
                    LFMPlaylist[i].setFound(true);
                }

            }

            return LFMPlaylist;
        }

        public Boolean exportPlaylist(List<TrackInfo> thePlaylist, int outputMethod) //Returns true for success, false for failure
        {
            if (outpath == null) //Don't let anyone export anything if the outpath is null
            {
                return false;
            }
            try
            {
                String theString = Global.FILESTART;

                foreach (TrackInfo theTrack in thePlaylist)
                {
                    if (theTrack.isFound() || theTrack.getScore() > minTotalSimilarity)//Don't add things that haven't been found -
                    {
                        theString = theString + "\n" + theTrack.getEXTInfo() + "," + theTrack.getArtist() + " - " + theTrack.getTrackName() + "\n" + theTrack.getFileName();
                    }
                }

                if (File.Exists(outpath))
                {
                    File.Delete(outpath);
                }

                byte[] theFile;

                //Let's save the file back in the right format
                switch (outputMethod)
                {
                    case Global.PLAYLISTFORMATDEFAULT:
                        theFile = Encoding.Convert(Encoding.Unicode, Encoding.Default, Encoding.Unicode.GetBytes(theString));
                        break;
                    case Global.PLAYLISTFORMATM3U:
                        theFile = Encoding.Convert(Encoding.Unicode, Encoding.Default, Encoding.Unicode.GetBytes(theString));
                        break;
                    case Global.PLAYLISTFORMATM3U8:
                        theFile = Encoding.Convert(Encoding.Unicode, Encoding.UTF8, Encoding.Unicode.GetBytes(theString));
                        break;
                    default:
                        theFile = Encoding.Convert(Encoding.Unicode, Encoding.Default, Encoding.Unicode.GetBytes(theString));
                        break;

                }

                File.WriteAllBytes(outpath, theFile);

            }
            catch (Exception ex)
            {
                return false;
            }

            return true;
        }

        public Boolean exportPlaylist(List<TrackInfo> thePlaylist, String iOutpath, int outputMethod) //Returns true for success, false for failure
        {
            outpath = iOutpath;
            return exportPlaylist(thePlaylist, outputMethod);
        }

    }
}
